﻿/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "Game/GameApplication.h"
#include <csignal>
#include "Ucop/Base/Console.h"
#include "Ucop/Util/Option.h"
#include "Ucop/System/Error.h"
#include "Ucop/Data/Rel/Details/MySQLConnector.h"
#include "Shared/Log/Log.h"
#include "Game/Server/WorldConnectionFactory.h"
#include "Game/Global.h"
#include "Game/World/WorldRunnable.h"
using Ucop::Util::Option;
using Ucop::Data::Rel::MySQLConnector;
using namespace Ucop;

/* 连接更新时间（毫秒） */
#define CONNECTION_UPDATE_INTERVAL  5000
/* 数据库Ping时间（秒） */
#define DATABASE_PING_INTERVAL      60

Session* _gLoginDB = NULL;
Session* _gCharaDB = NULL;
Session* _gWorldDB = NULL;


GameApplication::GameApplication(void)
    : Application(_L("Game"))
    , _MyconfPath(_L("./Game.conf"))
	, _MytcpServer(new WorldConnectionFactory)
    , _MystopEvent()
    , _Myrunning(true)

	, _MyloginDB()
	, _MycharaDB()
	, _MyworldDB()
{
	_gLoginDB = &_MyloginDB;
	_gCharaDB = &_MycharaDB;
	_gWorldDB = &_MyworldDB;
}

GameApplication::~GameApplication(void)
{

}

void GameApplication::OnDefineOptions(OptionSet& _Options)
{
    Application::OnDefineOptions(_Options);

    _Options.AddOption(
        Option(
        _L("conf"),
        _L("c"),
        _L("configuration file path"))
        .HasArg(true)
        .IsRequired(false));
}

int GameApplication::OnOption(const String& _Name, const String& _Arg)
{
    if (_Name == "conf") {
        _MyconfPath = _Arg;
    }
    return Application::OnOption(_Name, _Arg);
}

int GameApplication::OnInitialize(void)
{
    /* Load configuration */
    if (!sConfig.Open(_MyconfPath.c_str())) {
        System::PrintError("open config file: ");
        return EXIT_CONFIG;
    }

    /* Initialize logger */
    if (!sLog.Initialize(sConfig)) {
        System::PrintError("initialize logger: ");
        return EXIT_LOG;
    }

    sLog.outString("[Realmd]");
    sLog.outString("<Ctrl-C> to stop.\n"
        "  __  __      _  _  ___  ___  ___                         \n"
        " |  \\/  |__ _| \\| |/ __|/ _ \\/ __|        We Love      \n"
        " | |\\/| / _` | .` | (_ | (_) \\__ \\      Vanilla Wow    \n"
        " |_|  |_\\__,_|_|\\_|\\___|\\___/|___/                    \n"
        "                                        ____              \n"
        " For help and support please visit:    /_  /___ _ _ ___   \n"
        " Website: https://getmangos.eu          / // -_) '_/ _ \\ \n"
        "    Wiki: http://github.com/mangoswiki /___\\___|_| \\___/\n"
        );

    /* Initialize database */
    if (!MySQLConnector::Register()) {
        sLog.outError("Register MySQL connector failed");
    }

    sLoginDB.SetErrorHandler(OnLoginDBError);
    if (!sLoginDB.Open(MySQLConnector::MYSQL_KEY,
        sConfig.GetInt("Data.LoginDB.QueryConn", 3),
        sConfig.GetString("Data.LoginDB.Host", "127.0.0.1"),
        sConfig.GetString("Data.LoginDB.Port", "3306"),
        sConfig.GetString("Data.LoginDB.User", "mangos"),
        sConfig.GetString("Data.LoginDB.Password", "mangos"),
        sConfig.GetString("Data.LoginDB.Database", "realmd"))) {
        sLog.outError("Start Login database failed");
        return EXIT_FAILURE;
    }
    sLog.outString("LoginDB Server ver: %s", sLoginDB.GetServerInfo().c_str());
    sLog.outString("LoginDB Client ver: %s", sLoginDB.GetClientInfo().c_str());
    sLog.outString();

	sCharaDB.SetErrorHandler(OnCharaDBError);
	if (!sCharaDB.Open(MySQLConnector::MYSQL_KEY,
		sConfig.GetInt("Data.CharaDB.QueryConn", 3),
		sConfig.GetString("Data.CharaDB.Host", "127.0.0.1"),
		sConfig.GetString("Data.CharaDB.Port", "3306"),
		sConfig.GetString("Data.CharaDB.User", "mangos"),
		sConfig.GetString("Data.CharaDB.Password", "mangos"),
		sConfig.GetString("Data.CharaDB.Database", "characters"))) {
		sLog.outError("Start Character database failed");
		return EXIT_FAILURE;
	}
	sLog.outString("CharaDB Server ver: %s", sCharaDB.GetServerInfo().c_str());
	sLog.outString("CharaDB Client ver: %s", sCharaDB.GetClientInfo().c_str());
	sLog.outString();

	sWorldDB.SetErrorHandler(OnWorldDBError);
	if (!sWorldDB.Open(MySQLConnector::MYSQL_KEY,
		sConfig.GetInt("Data.WorldDB.QueryConn", 3),
		sConfig.GetString("Data.WorldDB.Host", "127.0.0.1"),
		sConfig.GetString("Data.WorldDB.Port", "3306"),
		sConfig.GetString("Data.WorldDB.User", "mangos"),
		sConfig.GetString("Data.WorldDB.Password", "mangos"),
		sConfig.GetString("Data.WorldDB.Database", "mangos"))) {
		sLog.outError("Start Character database failed");
		return EXIT_FAILURE;
	}
	sLog.outString("WorldDB Server ver: %s", sWorldDB.GetServerInfo().c_str());
	sLog.outString("WorldDB Client ver: %s", sWorldDB.GetClientInfo().c_str());
	sLog.outString();

	/* Initialize world */
	if (!sWorld.Initialize()) {
		sLog.outError("Initialize game world failed");
		return EXIT_FAILURE;
	}

	/* Start world thread */
	if (!sWorldRunnable.Start()) {
		sLog.outError("Start world thread failed");
		return EXIT_FAILURE;
	}

    /* Initialize ConnectionManager */
    sConnMgr.Initialize();

    /* Start TCP server */
    if (!_MytcpServer.Start( 
        sConfig.GetString("Net.Host", "0.0.0.0"), 
		sConfig.GetInt("Net.Port", 8085),
        sConfig.GetInt("Net.Backlog", 1024),
        sConfig.GetInt("Net.Workers", 0))) {
        sLog.outError("Start TCP server: %s", _MytcpServer.GetErrorString().c_str());
        return EXIT_FAILURE;
    }


    return EXIT_OK;
}

void GameApplication::OnSignal(int _Signal)
{
    Application::OnSignal(_Signal);

    if (_Signal == SIGINT) {
        sLog.outError("caught SIGINT, shutdown server");
        _Myrunning = false;
        _MystopEvent.Set();
    }
}


int GameApplication::Main(std::vector<String>& _UnknownArgs)
{
    HookSignal(SIGINT);
    sLog.outBasic("Game is running");

    for (;;)
    {
        /* Ping database */
		static int _DBPingInterval = DATABASE_PING_INTERVAL * 1000;
		static int _DBPingTime = _DBPingInterval;
        _DBPingTime += CONNECTION_UPDATE_INTERVAL;
		if (_DBPingTime >= _DBPingInterval)
        {
            _DBPingTime = 0;
            sLoginDB.Ping();
			sCharaDB.Ping();
			sWorldDB.Ping();

            sLog.outDebug("Ping database");
        }

        /* Update connections */
        sConnMgr.Update();

        /* Wait */
        _MystopEvent.Wait(CONNECTION_UPDATE_INTERVAL);
        if (!_Myrunning) {
            break;
        }
    }

    /* Close network listener */
    _MytcpServer.CloseListener();

	/* Stop world */
	sWorld.Stop();
	sWorldRunnable.Join();

    /* Close all connections */
    sConnMgr.Close();

    /* Close TCP server */
    _MytcpServer.Stop();

    return EXIT_OK;
}

void GameApplication::OnUninitialize(void)
{
    /* Unregister MySQL connector */
    MySQLConnector::Unregister();
}